# Program:  classifier.py
#
# Author:  Patrick Raycroft
#
# This program reads in the training data,
# normalizes it and outputs all features 
# normalized between [0.0, 1,0]

import re
import math
import copy

class kNNClass(object):
	def __init__(self, inFile, k):
		self.num_features = 7 

		self._neighbors = k
		self._maxT = []
		self._minT = []
		self._maxD = []
		self._minD = []
		f = open(inFile,'r')
		f.readline()
		f.readline()
		line = f.readline()
		f.close()
		line = line.split(":")[1]
		self.num_stats = len(line.split(',')) - 1  # Total number of stats

		# Initialize the maximums to -inf and minimums to +inf
		for n in range(self.num_stats):
			self._maxT.append(-9999)
			self._maxD.append(-9999)
			self._minT.append(9999)
			self._minD.append(9999)
		
		self.feature_indices = []
		self.feature_vector = []	

	def max_min(self, inFile):
	
		f = open(inFile, 'r')
		lines = f.readlines()
		f.close()
		for line in lines:
			l = line.split(":")
			if (l[0] != 'T1' and l[0] != 'D'): continue
			elif (l[0] == 'T1'): 			
				# Alter max and mins of teams if necessary	
				features = l[1].split(',')
				for n in range(self.num_stats):
					if (float(features[n]) > self._maxT[n]): self._maxT[n] = float(features[n])
					if (float(features[n]) < self._minT[n]): self._minT[n] = float(features[n])

			elif (l[0] == 'D'):
				# Alter max and min of difference if necessary
				features = l[1].split(',')
				for n in range(self.num_stats):
					if (float(features[n]) > self._maxD[n]): self._maxD[n] = float(features[n])
					if (float(features[n]) < self._minD[n]): self._minD[n] = float(features[n])

	def feature_select(self, inFile):
		feature_rating = []
		bad_features = [0,1,2,4,8,9,11,12,14,15,24,27,30,33,38,42,45,48,50]
		for n in range(self.num_stats): feature_rating.append(0.0)
		curr_class = 0
		f = open(inFile,'r')
		lines = f.readlines()
		f.close()
		for line in lines:
			l = line.split(":")
			if (l[0] == 'C'):
				curr_class = int(l[1])
				continue

			if (l[0] == 'D' and curr_class == 1):
				stats = l[1].split(',')
				for i in range(self.num_stats):
					val = (float(stats[i]) - self._minD[i]) / (self._maxD[i] - self._minD[i])
					feature_rating[i] += val

		feature_idx = []
		selected = self.num_features
		while (selected > 0):
			max = 0
			maxIdx = 0
			for idx,f in enumerate(feature_rating):
				if (f > max):
					max = f
					maxIdx = idx
			feature_rating[maxIdx] = -1
			if (maxIdx not in bad_features): 
				self.feature_indices.append(maxIdx)
				selected -= 1

	def create_feature_vector(self, inFile):
		f = open(inFile,'r')
		while True:
			instance = []
			game_line = f.readline()
			if not game_line: break
			instance.append(int(game_line.split(":")[1].rstrip('\n')))
			#game = game_line.split(":")[1]
			instance.append(int(f.readline().split(":")[1].rstrip('\n')))
			t1Full = f.readline().split(":")[1].rstrip('\n')
			t1Full = t1Full.split(',')
			for i in range(self.num_stats):
				if (i in self.feature_indices):
					val = (float(t1Full[i]) - self._minT[i]) / (self._maxT[i] - self._minT[i])
					instance.append(val)
			t2Full = f.readline().split(":")[1].rstrip('\n')
			t2Full = t2Full.split(',')
			for i in range(self.num_stats):
				if (i in self.feature_indices):
					val = (float(t2Full[i]) - self._minT[i]) / (self._maxT[i] - self._minT[i])
					instance.append(val)
			dFull = f.readline().split(":")[1].rstrip('\n')
			dFull = dFull.split(',')
			for i in range(self.num_stats):
				if (i in self.feature_indices):
					val = (float(dFull[i]) - self._minD[i]) / (self._maxD[i] - self._minD[i])
					instance.append(val)
			
			self.feature_vector.append(instance)

	def classify(self, testFile):
		gameClass = []
		filepath = 'teamstats_'
		f = open(testFile, 'r')
		lines = f.readlines()
		f.close()
		for line in lines:
			gids_found = []
			gameVec  = []
			t1Vec = []
			t2Vec = []
			dVec = []
			l = line.split(',')
			t1 = l[1]
			t1 = re.sub(r'[^\w-]','',re.sub(r' ','-', t1.strip().lower()))
			t2 = l[2]
			t2 = re.sub(r'[^\w-]','',re.sub(r' ','-', t2.strip().lower()))
			statFile = open(filepath + l[0] + '.csv', 'r')
			statLine = statFile.readlines()
			statFile.close()
			for sline in statLine:
				s = sline.split(',')
				if (s[0] == t1):
					for i in range(self.num_stats):
						if (i in self.feature_indices): t1Vec.append(float(s[i + 2]))
				if (s[0] == t2):
					for i in range(self.num_stats):
						if (i in self.feature_indices): t2Vec.append(float(s[i + 2]))
			gameVec.append(0)
			gameVec.append(0)
			for item in t1Vec:
				gameVec.append(item)
			for item in t2Vec:
				gameVec.append(item)
			for diff in range(len(self.feature_indices) - 1):
				val = float(t1Vec[diff]) - float(t2Vec[diff])
				gameVec.append(val)			

			distances = [] # List of (gameID, distance, class) tuples
			for inst in self.feature_vector:
				d2 = 0
				for idx in range(len(inst) - 1):
					if (idx == 0): gid = inst[idx]
					elif (idx == 1): c = inst[idx]
					else:
						d2 += math.pow(inst[idx] - gameVec[idx], 2) 
				d = math.sqrt(d2)
				distances.append( (gid, d, c) )
			# Remove duplicates
			idx_to_pop = []
			for i1,d1 in enumerate(distances):
				gid1 = d1[0]
				for i2,d2 in enumerate(distances):
					gid2 = d2[0]
					if (gid1 == gid2):
						if (d1[1] == d2[1]): continue
						if (d1[1] < d2[1]): 
							if (i2 not in idx_to_pop): idx_to_pop.append(i2)
						else: 
							if (i1 not in idx_to_pop): idx_to_pop.append(i1)
			idx_to_pop.reverse()
			for i in idx_to_pop:
				distances.pop(i)

			nearest_idx = []
			selected = self._neighbors
			duplicate_dist = copy.copy(distances)
			while (selected > 0):
				min = 9999
				minIdx = -1
				for idx,n in enumerate(duplicate_dist):
					if (n == 99999): continue	
					if (n[1] < min):
						min = n[1]
						minIdx = idx
				duplicate_dist[minIdx] = 99999
				nearest_idx.append(minIdx)
				selected -= 1

			classifier = 0
			for n in nearest_idx:
				if (distances[n] == 99999): continue	
				classifier += (1 / distances[n][1]) * distances[n][2]
		
				
			if (classifier < 0): gameClass.append( (-1, classifier) )
			else: gameClass.append( (1, classifier) )

		return gameClass 
				
